package edu.zju.udms.dnavigator.core.repository;

import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import edu.zju.udms.dnavigator.core.entity.MetaLuceneMapper;
import edu.zju.udms.dnavigator.core.entity.MetaObject;

public class LuceneDataset {
	private static final Logger logger = Logger.getLogger(LuceneDataset.class);

	private IndexWriter indexWriter;
	private IndexSearcher indexSearcher;
	private QueryParser queryParser;
	private Sort defaultSort; 
	private boolean forceMerge;
	private int maxNumSegments;
	private MetaLuceneMapper mlp=MetaLuceneMapper.getInstance();
	
	public LuceneDataset(URI dirURI, OpenMode mode, double bufferSize,String defaultQueryField, Analyzer analyzer,Sort defaultSort,boolean forceMerge,int maxNumSegments) throws IOException {
		Directory directory = FSDirectory.open(Paths.get(dirURI));
		IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
		iwc.setOpenMode(mode);
		if (bufferSize > 0)
			iwc.setRAMBufferSizeMB(bufferSize);
		indexWriter = new IndexWriter(directory, iwc);
		indexWriter.commit();
		IndexReader indexReader=DirectoryReader.open(indexWriter, true);
		indexSearcher = new IndexSearcher(indexReader);
		if (analyzer != null)
			analyzer = new StandardAnalyzer();
		queryParser = new QueryParser(defaultQueryField, analyzer);
		if (defaultSort != null)
			defaultSort=new Sort();
		this.defaultSort=defaultSort;
		this.forceMerge=forceMerge;
		this.maxNumSegments=maxNumSegments;
	}

	public void commit() throws Exception{
		indexWriter.commit();
		//force merge
		if(forceMerge){
			indexWriter.forceMerge(maxNumSegments);
		}
		//refresh the reader
		indexSearcher.getIndexReader().close();
		IndexReader indexReader=DirectoryReader.open(indexWriter, true);
		indexSearcher = new IndexSearcher(indexReader);
	}
	
	@Override
	public void finalize() {
		try {
			indexWriter.close();
			indexSearcher.getIndexReader().close();
			indexWriter.getDirectory().close();
		} catch (IOException e) {
			logger.error("", e);
		}
	}
	
	public boolean add(Document doc){
		boolean success=true;
		try {
			indexWriter.addDocument(doc);
			
		} catch (IOException e) {
			logger.error("",e);
			success=false;
		}
		return success;
	}
	
	public boolean delete(Query query){
		boolean success=true;
		try {
			indexWriter.deleteDocuments(query);
		} catch (IOException e) {
			logger.error("",e);
			success=false;
		}
		return success;
	}
	
	public TopDocs search(Query query, int size, Sort sort) {
		TopDocs result;
		try {
			result = indexSearcher.search(query, size, sort);
		} catch (IOException e) {
			logger.error("",e);
			result = new TopDocs(0, null, 0);
		}
		logger.info(String.format("query search require size : %d exact size: %d",size,result.totalHits));
		return result;
	}

	public TopDocs search(Query query, int size) {
		return search(query,size,defaultSort);
	}

	public TopDocs search(String query, int size,Sort sort) {
		Query queryObj=null;
		try {
			queryObj = queryParser.parse(query);
		} catch (ParseException e) {
			logger.error("",e);
		}
		return search(queryObj,size,sort);
	}

	public TopDocs search(String query, int size) {
		return search(query,size,defaultSort);
	}
	
	public Document getDoc(int docId) {
		Document doc=null;
		try {
			doc=indexSearcher.doc(docId);
		} catch (IOException e) {
			logger.error("",e);
			doc=new Document();
		}
		return doc;
	}
	
	public List<String> getFieldNames() {
		ArrayList<String> res = new ArrayList<>();
		List<LeafReaderContext> leaves = indexSearcher.getIndexReader().leaves();
		for(LeafReaderContext leaf:leaves) {
			LeafReader leafReader = leaf.reader();
			try {
				Fields fields = leafReader.fields();
				Iterator<String> iter = fields.iterator();
				while(iter.hasNext()) {
					res.add(iter.next());
				}
			} catch (IOException e) {
				logger.error("", e);
				continue;
			}
		}
		return res;
	}
	
	public List<? extends MetaObject> parseDocsToMeta(TopDocs topDocs){
		List<MetaObject> result=new ArrayList<>();
		ScoreDoc[] docs = topDocs.scoreDocs;
		for(ScoreDoc doc : docs){
			result.add(mlp.doc2meta(getDoc(doc.doc)));
		}
		return result;
	}
	
	public List<Map<String,String>> parseDocsToMap(TopDocs topDocs){
		List<Map<String,String>> result = new ArrayList<>();
		ScoreDoc[] docs = topDocs.scoreDocs;
		for(ScoreDoc doc : docs) {
			result.add(mlp.doc2map(getDoc(doc.doc)));
		}
		return result;
	}
	
	public void deleteAll() throws IOException{
		indexWriter.deleteAll();
		indexWriter.commit();
	}
}
